package hydrap2p.upload;

import hydrap2p.logging.Level;

import java.util.LinkedList;
import java.util.List;

/**
 * Implements a queue of uploads waiting to go
 * Only allows MAXUPLOADS=5 of active uploads
 * Does not allow multiple copies of (protocol, ip, port, uid) uploads
 * 
 * @author Hufman
 *
 */
public class UploadQueue {

	private List<UploadQueued> queue;
	private List<UploadQueued> activequeue;
	private UploadProtocols protocols;
	private UploadListeners listeners;
	
	private static final int MAXUPLOADS=5;
	private static final int MAXWAITINGUPLOADS=50;
	
	public UploadQueue(UploadProtocols protocols, UploadListeners listeners)
	{
		queue=new LinkedList<UploadQueued>();
		activequeue=new LinkedList<UploadQueued>();
		this.listeners=listeners;
		listeners.setUploadQueue(this);
		this.protocols=protocols;
		protocols.setUploadQueue(this);
	}
	
	public boolean enqueueUpload(UploadQueued item)
	{
		if (protocols.getProtocol(item.protocol)==null)
		{
			UploadLogging.log(Level.MISHAP, "Upload - Upload Queue", "Could not enqueue upload from "+item.ip+":"+item.port+", the protocol '"+item.protocol+"' is not registered");
			return false;
		}
		synchronized(queue)
		{
			if (queue.size()<MAXWAITINGUPLOADS && !containsSimilar(queue,item) && !containsSimilar(activequeue,item))
				queue.add(item);
		}
		listeners.newUpload(item);
		startUploads();
		return true;
	}
	
	public List<UploadQueued> getUploadQueue()
	{
		List<UploadQueued> ret;
		synchronized(queue)
		{
			ret=new LinkedList<UploadQueued>(activequeue);
			ret.addAll(queue);
		}
		return ret;
	}
	
	public boolean containsSimilar(List<UploadQueued> list, UploadQueued needle)
	{
		for (UploadQueued finding : list)
		{
			if (similar(finding, needle))
				return true;
		}
		return false;
	}
	
	public boolean similar(UploadQueued left, UploadQueued right)
	{
		return left.uid==right.uid &&
			left.protocol.equals(right.protocol) &&
			left.ip.equals(right.ip) &&
			left.port==right.port;
	}
	
	/**
	 * Finds a similar upload, and sets the progress and protocolinfo to match
	 * @param item
	 */
	public void updateUpload(UploadQueued item)
	{
		boolean found=false;
		synchronized(queue)
		{
			for (UploadQueued myitem: activequeue)
			{
				if (similar(myitem,item))
				{
					myitem.setProgress(item.progress);
					myitem.setProtocolInfo(item.protocolInfo);
					found=true;
					break;
				}
			}
			if (!found)
			{
				for (UploadQueued myitem: queue)
				{
					if (similar(myitem,item))
					{
						myitem.setProgress(item.progress);
						myitem.setProtocolInfo(item.protocolInfo);
						found=true;
						break;
					}
				}				
			}
		}
		if (found)
			listeners.updateUpload(item);
	}
	
	public boolean removeUpload(UploadQueued item)
	{
		boolean ret=false;
		synchronized(queue)
		{
			for (UploadQueued myitem: activequeue)
			{
				if (similar(myitem,item))
				{
					activequeue.remove(myitem);
					ret=true;
					break;
				}
			}
			for (UploadQueued myitem: queue)
			{
				if (similar(myitem,item))
				{
					queue.remove(myitem);
					ret=true;
					break;
				}
			}
		}
		protocols.cancelUpload(item);	// cancel it in case a protocol has started to upload it
		listeners.removedUpload(item);
		return ret;
	}
	
	/**
	 * Looks through the list of uploads to remove any uploads to this protocol
	 * @param protocol
	 */
	public void removeProtocolUploads(String protocol)
	{
		List<UploadQueued> protocoluploads=new LinkedList<UploadQueued>();
		synchronized(queue)
		{
			for (UploadQueued item : activequeue)
			{
				if (item.protocol.equals(protocol))
					protocoluploads.add(item);
			}
			for (UploadQueued item : queue)
			{
				if (item.protocol.equals(protocol))
					protocoluploads.add(item);
			}
		}
		for (UploadQueued item:protocoluploads)
			removeUpload(item);
	}
	
	private void startUploads()
	{
		synchronized(queue)
		{
			while (activequeue.size()<MAXUPLOADS && queue.size()>0)		// if we are not uploading all we could
			{
				UploadQueued item=queue.remove(0);
				boolean success=protocols.upload(item);
				if (success)
					activequeue.add(item);
			}
		}
	}
	
	public boolean syncListener(UploadListenerIface listener)
	{
		synchronized(queue)
		{
			try {
				for (UploadQueued item:activequeue)
				{
					listener.newUpload(item);
				}
				for (UploadQueued item:queue)
				{
					listener.newUpload(item);
				}
			}
			catch (Exception e)
			{
				return false;
			}
		}
		return true;
	}
}
